/*
 * Copyright (C) 2001-2006 the xine-project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * $Id: lirc.c,v 1.22 2006/04/04 22:07:32 dsalt Exp $
 *
 * lirc (infrared control) code, originally based on oxine
 */

#include "globals.h"
#include "defs.h"

#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>

#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <glib.h>
#include <gtk/gtk.h>

#ifdef HAVE_LIRC
#include <lirc/lirc_client.h>
#endif

#include "lirc.h"
#include "engine.h"

#ifdef HAVE_LIRC
static pthread_t             gxine_lirc_thread;
static pthread_mutex_t	     lirc_init_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t	     lirc_init_cond = PTHREAD_COND_INITIALIZER;
static struct lirc_config   *lircconfig;
static int                   gxine_lirc_fd;

/* lirc thread
  primary config file for lirc is ~/.gxine/lircrc
  secondary is ~/.lircrc
  prog name = gxine
  possible config = lots :-)
*/

static int gxine_lirc_tryload (const char *file)
{
  struct stat st;
  char *fname = g_build_filename (g_get_home_dir(), file, NULL);

  if (stat (fname, &st))
  {
    if (errno == ENOENT)
    {
      free (fname);
      return 1;
    }
    g_printerr (_("lirc: problem with ~/%s: %s\n"), file, strerror (errno));
    free (fname);
    return 1;
  }

  if (lirc_readconfig (fname, &lircconfig, NULL) != 0)
  {
    g_printerr(_("lirc: lirc_readconfig of %s failed\n"), fname);
    free (fname);
    return 1;
  }

  free (fname);
  return 0;
}

static void gxine_lirc_cleanup (void *data)
{
  lirc_deinit ();
}

static __attribute__ ((noreturn)) void *gxine_lirc_run (void *data)
{
  char *config;
  gboolean first = TRUE;

  for (;;)
  {
    pthread_mutex_lock (&lirc_init_mutex);

    gxine_lirc_fd = -1;
    while (gxine_lirc_fd == -1)
    {
      if ((gxine_lirc_fd = lirc_init ("gxine", 0)) != -1)
      {
        if (!first)
          logprintf ("lirc: reconnected\n");
        break;
      }

      if (!first)
      {
        sleep (1);
        continue;
      }

      g_printerr (_("lirc: cannot initialise - disabling remote control\n"
	         "lirc: maybe lircd isn't running or you can't connect to the socket?\n"));
      failed:
      gxine_lirc_thread = 0;
      pthread_cond_signal (&lirc_init_cond);
      pthread_mutex_unlock (&lirc_init_mutex);
      pthread_exit(NULL);
    }

    first = FALSE;
    pthread_cleanup_push (gxine_lirc_cleanup, NULL);

    /* read ~/.gxine/lircrc then ~/.lircrc - definitions are not overridden */
    if ((gxine_lirc_tryload (".gxine/lircrc")
	 && gxine_lirc_tryload (".lircrc"))
	|| !lircconfig || !lircconfig->first)
    {
      /* if neither file has been read successfully or there's nothing for
       * gxine, exit now */
      g_printerr (_("lirc: no configuration found - disabling remote control\n"));
      goto failed;
    }

    pthread_cond_signal (&lirc_init_cond);
    pthread_mutex_unlock (&lirc_init_mutex);

    for (;;)
    {
      char *code;
      pthread_testcancel();

      if (lirc_nextcode (&code) == 0)
      {
        pthread_testcancel();

        if (code != NULL)
        {
	  int ret;
          while ((ret = lirc_code2char (lircconfig, code, &config) == 0)
		 && config != NULL)
	  {
	    char *src = g_strdup_printf (_("LIRC binding %s.%s"),
					 lircconfig->current_mode, code);
	    engine_queue_push (config, NULL, NULL, NULL, NULL, src);
	    free (src);
	  }

	  free(code);
        }
      }
      else
      {
	/* lircd has exited? try reinitialising in case it's been restarted */
	logprintf ("lirc: socket read error\n");
        pthread_testcancel();
        break;
      }
    }

    pthread_cleanup_pop (1);
  }
}

static void gxine_lirc_start (void)
{
  pthread_attr_t attr;
  gxine_lirc_thread = 0;
  pthread_mutex_lock (&lirc_init_mutex);
  pthread_attr_init (&attr);
  pthread_attr_setstacksize (&attr, 256 * 1024); /* probably overkill */
  if (!pthread_create (&gxine_lirc_thread, &attr, gxine_lirc_run, NULL))
    pthread_cond_wait (&lirc_init_cond, &lirc_init_mutex);
  pthread_attr_destroy (&attr);
  pthread_mutex_unlock (&lirc_init_mutex);
}

#else
#define gxine_lirc_thread (0)
#define gxine_lirc_start()
#endif

void gxine_lirc_quit (void)
{
#ifdef HAVE_LIRC
  if (gxine_lirc_thread)
  {
    pthread_cancel (gxine_lirc_thread);
    lirc_freeconfig (lircconfig);
  }
#endif
}

static JSBool js_reload_lirc (JSContext *cx, JSObject *obj, uintN argc,
			      jsval *argv, jsval *rval)
{
  se_log_fncall ("reload_lirc");
  se_argc_check_max (0, "reload_lirc");
  gxine_lirc_quit ();
  if (gxine_lirc_thread)
    pthread_join (gxine_lirc_thread, NULL);
  gxine_lirc_start ();
  *rval = gxine_lirc_thread ? JSVAL_TRUE : JSVAL_FALSE;
  return JS_TRUE;
}

void gxine_lirc_init (void)
{
  se_defun (gse, NULL, "reload_lirc", js_reload_lirc, 0, 0,
	    SE_GROUP_HIDDEN, NULL, NULL);
  gxine_lirc_start ();
}
